qtre.renderable = function(name, arrays, opt_mode){

	this.name = name;

	this.owner = null;

	this.visible = true;

	this._onRenderBegins = [];

	this._onRenderEnds = [];
	/**
	 * it will assign worldViewProjection, viewInverse, worldMatrix, worldInverseTranspose automatically to
	 * the shader when it is set true;
	 */
	this.updateMatrixAuto = true;
	
	this.materials = new Array();

	this.primitives = [];
	
	if(qtre.core.isset(arrays)){
		this.addPrimitive(arrays);
	}

	this.worldMatrix = new Float32Array(16);
	tdl.fast.matrix4.identity(this.worldMatrix);

	this.mode = (opt_mode === undefined) ? gl.TRIANGLES : opt_mode;
}

qtre.renderable.prototype = {

	addRenderBeginCallback : function(callback){
		this._onRenderBegins.push(callback);
	},

	removeRenderBeginCallback : function(callback){
		for(var i = 0;  i < this._onRenderBegins.length; i++){
			if(this._onRenderBegins[i] == callback){
				delete this._onRenderBegins[i];
			}
		}
	},

	addRenderEndCallback : function(callback){
		this._onRenderEnds.push(callback);
	},

	removeRenderEndCallback : function(callback){
		for(var i = 0;  i < this._onRenderEnds.length; i++){
			if(this._onRenderEnds[i] == callback){
				delete this._onRenderEnds[i];
			}
		}
	},

	onRenderBegin : function(material){
		for(var i = 0;  i < this._onRenderBegins.length; i++){
			this._onRenderBegins[i].call(this, material);
		}
	},

	onRenderEnd : function(material){
		for(var i = 0;  i < this._onRenderEnds.length; i++){
			this._onRenderEnds[i].call(this, material);
		}
	},
	
	updateWorldMatrix : function(){
		if(!this.owner){
			return ;
		}
		var sceneNode = this.owner;
		var worldMatrix = new Float32Array(16);
		tdl.fast.matrix4.copy(worldMatrix, sceneNode.worldMatrix);
		while(sceneNode.parent){
			tdl.fast.matrix4.mul(worldMatrix,worldMatrix,sceneNode.parent.worldMatrix);
			sceneNode = sceneNode.parent;
		}
		this.worldMatrix = worldMatrix;
	},

	render : function(material){
		if(!this.visible){
			return ;
		}
		if(!this.owner){
			return ;
		}
		if(this.updateMatrixAuto){
			//set all the matrix needed in the shader
			if(material.getCamera()){
				var viewProjection = material.getCamera().getViewProjection();
				var worldViewProjection = new Float32Array(16);
				tdl.fast.matrix4.mul(worldViewProjection, this.worldMatrix, viewProjection);

				var program = material.getProgram();
				if(material.hasUniform("worldViewProjection")){
					program.setUniform("worldViewProjection", worldViewProjection);
				}
				if(material.hasUniform("viewInverse")){
					program.setUniform("viewInverse", material.getCamera().getViewInverse());
				}
				if(material.hasUniform("world")){
					program.setUniform("world", this.worldMatrix);
				}
				if(material.hasUniform("worldInverseTranspose")){
					program.setUniform("worldInverseTranspose", this.getWorldInverseTranspose());
				}
			}
		}
		for(var i = 0; i < this.primitives.length; i++){
			var buffers = this.primitives[i];
			for (var name in buffers) {
				var b = buffers[name];
				if (name == 'indices') {
					qtre.core.gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.buffer());
				} else {
					var attrib = material.getProgram().attrib[name];
					if (attrib) {
						attrib(b);
					}
				}
			}
			if(!qtre.core.isset(buffers.indices)){
				qtre.core.gl.drawArrays(this.mode, 0, buffers.position.totalComponents()/3, gl.UNSIGNED_SHORT, 0);
			}else{
				qtre.core.gl.drawElements(this.mode, buffers.indices.totalComponents(), gl.UNSIGNED_SHORT, 0);
			}
		}

	},

	bindMaterial : function(material){
		this.materials[material.name] = material;
		material.addRenderable(this);
	},

	unbindMaterial : function(material){
		delete this.materials[material.name];
		material.removeRenderable(material);
	},

	attachObject : function(object){
		this.object = object;
	},

	addPrimitive : function(arrays){
		var b = this.createBuffers(arrays);
		this.primitives.push(b);
	},

	createBuffer : function(name, array){
		var target = (name == 'indices') ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
		b = new tdl.buffers.Buffer(array, target);
		return b;
	},
	
	createBuffers : function(arrays){
		var buffers = new Array();
		for(var name in arrays){
			buffers[name] = this.createBuffer(name, arrays[name]);
		}
		return buffers;
	},

	getWorldInverseTranspose : function(){
		var worldInverse = new Float32Array(16);
		var worldInverseTranspose = new Float32Array(16);
		tdl.fast.matrix4.inverse(worldInverse, this.worldMatrix);
		tdl.fast.matrix4.transpose(worldInverseTranspose, worldInverse);
		return worldInverseTranspose;
	}

}
